using System;
using System.Collections.Generic;
using System.Threading;

namespace FreshMeat.Xaml
{
   /// <summary>
   /// Simple context which only allows a single operation to occur at a time while refreshing all
   /// associated <see cref="Command"/>s so they disable/enable appropriately.  Also supports 
   /// arbitrary actions that are notified as operations start and finish, which can be used
   /// to drive "IsBusy" or similar properties.
   /// </summary>
   public class OperationContext : ViewModel, IDisposable
   {
      const string NobodyName = "(nobody)";
      readonly List<NamedAction> actions = new List<NamedAction>();
      int executeFlag;
      string executingName = NobodyName;

      public void Notify(CommandBase command)
      {
         actions.Add(new NamedAction {Action = _ => command.NotifyCanExecuteChanged(), Name = command.Name ?? "UnnamedCommand"});
      }

      public void Notify(Action<bool> action, string name = null)
      {
         actions.Add(new NamedAction {Action = action, Name = name ?? "UnnamedAction"});
      }

      /// <summary>
      /// Tries to start an operation.
      /// If no other operation is executing, returns true to indicate the operation should start.
      /// If another operation is already executing, returns false to indicate the operation should NOT start.
      /// </summary>
      public bool TryStart(string name = null)
      {
         var result = Interlocked.CompareExchange(ref executeFlag, 1, 0) == 0;
         if (result)
         {
            executingName = name;
            //if (!string.IsNullOrEmpty(executingName))
            //   System.Diagnostics.Trace.TraceInformation(string.Format("Operation '{0}' is starting.", executingName));
            Props.ChangedAll();
            DoActions(true, true); // Always throw exceptions when starting since we are (supposedly) on the UI thread
         }
         return result;
      }

      /// <summary>
      /// Starts an operation.
      /// An exception is thrown if another operation is already executing.
      /// </summary>
      /// <returns>Returns the current instance as a convenience so this method can be called in a 'using' block.</returns>
      public OperationContext Start(string name = null)
      {
         if (!TryStart(name))
            throw new InvalidOperationException("Another operation is already running.");
         return this;
      }

      /// <summary>
      /// Completes an operation.
      /// </summary>
      public void Complete(bool throwOnError = false)
      {
         if (executeFlag == 0)
            return;
         //if (!string.IsNullOrEmpty(executingName))
         //   System.Diagnostics.Trace.TraceInformation(string.Format("Operation '{0}' has completed.", executingName));
         executingName = NobodyName;
         executeFlag = 0;
         Props.ChangedAll();
         DoActions(false, throwOnError);
      }

      /// <summary>
      /// Indicates whether an oepration is currently executing.
      /// </summary>
      public bool IsExecuting
      {
         get { return executeFlag == 1; }
      }

      /// <summary>
      /// Optional name for debugging purposes.
      /// </summary>
      public string WhoIsExecuting
      {
         get { return executingName; }
      }

      void DoActions(bool isExecuting, bool throwOnError)
      {
         foreach (var namedAction in actions)
         {
            try
            {
               namedAction.Action(isExecuting);
            }
            catch (Exception e)
            {
               if (throwOnError)
                  throw;

               System.Diagnostics.Trace.TraceError(string.Format("OperationContext suppressed exception by action '{0}': {1}", namedAction.Name, e.Message));
            }
         }
      }

      struct NamedAction
      {
         public string Name;
         public Action<bool> Action;
      }

      public void Dispose()
      {
         Complete();
      }
   }
}